package com.nowcoder.topic.dp.hard;

import java.util.Arrays;

/**
 * NC173 填充数组
 * @author d3y1
 */
public class NC173{
    private final int MOD = 1000000007;

    /**
     * 代码中的类名、方法名、参数名已经指定，请勿修改，直接返回方法规定的值即可
     *
     *
     * @param a int整型一维数组
     * @param k int整型
     * @return int整型
     */
    public int FillArray (int[] a, int k) {
//        return solution1(a, k);
        return solution2(a, k);
    }

    /**
     * 动态规划
     *
     * left  取值左边界 2
     * right 取值右边界 5
     * 区间取值个数j = right - left + 1 = 5 - 2 + 1 = 4 (2,3,4,5)
     * dp[j]表示以区间中某值为开始的填充方案数(其实与具体的取值无关, 仅与取值区间大小有关)
     *
     * 举例子找规律:
     * 2 0 5
     *   2
     *   3
     *   4
     *   5
     *
     * 填充0个数i: 1 (0)
     * 取值个数j: 4 (2,3,4,5)
     * dp[2] = 1
     * dp[3] = 1
     * dp[4] = 1
     * dp[5] = 1
     * 等价于
     * dp[1] = 1
     * dp[2] = 1
     * dp[3] = 1
     * dp[4] = 1
     *
     * 2 0 0 5
     *   2 2
     *   2 3
     *   2 4
     *   2 5
     *
     *   3 3
     *   3 4
     *   3 5
     *
     *   4 4
     *   4 5
     *
     *   5 5
     *
     * 填充0个数i: 2 (0 0)
     * 取值个数j: 4 (2,3,4,5)
     * dp[2] = 4
     * dp[3] = 3
     * dp[4] = 2
     * dp[5] = 1
     * 等价于
     * dp[1] = 4
     * dp[2] = 3
     * dp[3] = 2
     * dp[4] = 1
     *
     * 2 0 0 0 5
     *   2 2 2
     *   2 2 3
     *   2 2 4
     *   2 2 5
     *   2 3 3
     *   2 3 4
     *   2 3 5
     *   2 4 4
     *   2 4 5
     *   2 5 5
     *
     *   3 3 3
     *   3 3 4
     *   3 3 5
     *   3 4 4
     *   3 4 5
     *   3 5 5
     *
     *   4 4 4
     *   4 4 5
     *   4 5 5
     *
     *   5 5 5
     *
     * 填充0个数i: 3 (0 0 0)
     * 取值个数j: 4 (2,3,4,5)
     * dp[2] = 10
     * dp[3] = 6
     * dp[4] = 3
     * dp[5] = 1
     * 等价于
     * dp[1] = 10
     * dp[2] = 6
     * dp[3] = 3
     * dp[4] = 1
     *
     * 可见
     * i / j 1  2  3  4
     * 1     1  1  1  1
     * 2     4  3  2  1
     * 3     10 6  3  1
     *
     * @param a
     * @param k
     * @return
     */
    private int solution1(int[] a, int k){
        long[] dp = new long[1001];

        int n = a.length;
        int[] A = new int[n+2];
        for(int i=1; i<=n; i++){
            A[i] = a[i-1];
        }
        // 补充左右边界 便于后续处理
        A[0] = 1;
        A[n+1] = k;

        int left = A[0];
        int right = 0;
        int gap = 0;
        long sum = 0;
        long result = 0;
        for(int i=1; i<n+2; i++){
            if(A[i] > 0){
                if(gap == 0){
                    left = A[i];
                }else if(gap > 0){
                    right = A[i];
                    Arrays.fill(dp, 1);
                    for(int times=1; times<=gap; times++){
                        sum = 0;
                        for(int j=right-left+1; j>=1; j--){
                            sum = (sum + dp[j]) % MOD;
                            dp[j] = sum;
                        }
                    }
                    if(result == 0){
                        result = sum % MOD;
                    }else if(result > 0){
                        result = (result * sum) % MOD;
                    }
                    left = right;
                    gap = 0;
                }
            }else if(A[i] == 0){
                gap++;
            }
        }

        return (int)result;
    }

    /**
     * 动态规划
     *
     * left  取值左边界 2
     * right 取值右边界 5
     * 区间取值个数j = right - left + 1 = 5 - 2 + 1 = 4 (2,3,4,5)
     * dp[j]表示以区间中某值为开始的填充方案数(其实与具体的取值无关, 仅与取值区间大小有关)
     *
     * 举例子找规律:
     * 2 0 5
     *   2
     *   3
     *   4
     *   5
     *
     * 填充0个数i: 1 (0)
     * 取值个数j: 4 (2,3,4,5)
     * dp[2] = 1
     * dp[3] = 1
     * dp[4] = 1
     * dp[5] = 1
     * 等价于
     * dp[1] = 1
     * dp[2] = 1
     * dp[3] = 1
     * dp[4] = 1
     *
     * 2 0 0 5
     *   2 2
     *   2 3
     *   2 4
     *   2 5
     *
     *   3 3
     *   3 4
     *   3 5
     *
     *   4 4
     *   4 5
     *
     *   5 5
     *
     * 填充0个数i: 2 (0 0)
     * 取值个数j: 4 (2,3,4,5)
     * dp[2] = 4
     * dp[3] = 3
     * dp[4] = 2
     * dp[5] = 1
     * 等价于
     * dp[1] = 1
     * dp[2] = 2
     * dp[3] = 3
     * dp[4] = 4
     *
     * 2 0 0 0 5
     *   2 2 2
     *   2 2 3
     *   2 2 4
     *   2 2 5
     *   2 3 3
     *   2 3 4
     *   2 3 5
     *   2 4 4
     *   2 4 5
     *   2 5 5
     *
     *   3 3 3
     *   3 3 4
     *   3 3 5
     *   3 4 4
     *   3 4 5
     *   3 5 5
     *
     *   4 4 4
     *   4 4 5
     *   4 5 5
     *
     *   5 5 5
     *
     * 填充0个数i: 3 (0 0 0)
     * 取值个数j: 4 (2,3,4,5)
     * dp[2] = 10
     * dp[3] = 6
     * dp[4] = 3
     * dp[5] = 1
     * 等价于
     * dp[1] = 1
     * dp[2] = 3
     * dp[3] = 6
     * dp[4] = 10
     *
     * 可见
     * i / j 1  2  3  4
     * 1     1  1  1  1
     * 2     1  2  3  4
     * 3     1  3  6  10
     *
     * 由上可知
     * dp[i][j]表示填充个数为i且取值个数为j时的填充方案数
     * dp[i][j] = dp[i-1][j] + dp[i][j-1]
     *
     * @param a
     * @param k
     * @return
     */
    private int solution2(int[] a, int k){
        int n = a.length;

        int[][] dp = new int[n+1][k+1];

        for(int j=1; j<=k; j++){
            dp[1][j] = j;
        }
        for(int i=1; i<=n; i++){
            for(int j=1; j<=k; j++){
                if(i == 1){
                    dp[i][j] = j;
                }else{
                    dp[i][j] = (dp[i-1][j] + dp[i][j-1]) % MOD;
                }
            }
        }

        int[] A = new int[n+2];
        for(int i=1; i<=n; i++){
            A[i] = a[i-1];
        }
        // 补充左右边界 便于后续处理
        A[0] = 1;
        A[n+1] = k;

        int left = A[0];
        int right = 0;
        int gap = 0;
        long sum = 0;
        long result = 1;
        for(int i=1; i<n+2; i++){
            if(A[i] > 0){
                if(gap == 0){
                    left = A[i];
                }else if(gap > 0){
                    right = A[i];
                    sum = dp[gap][right-left+1];
                    result = (result * sum) % MOD;
                    left = right;
                    gap = 0;
                }
            }else if(A[i] == 0){
                gap++;
            }
        }

        return (int)result;
    }
}